package nveces.jspwiki.plugins.pdf;

import static nveces.jspwiki.plugins.pdf.MagicNamesPluginPdf.*;

import static nveces.jspwiki.plugins.pdf.MagicNamesPluginPdf.ENCODING_ISO_8859_1;
import static nveces.jspwiki.plugins.pdf.MagicNamesPluginPdf.HTML_HREF;
import static nveces.jspwiki.plugins.pdf.MagicNamesPluginPdf.HTML_TAG_LINK;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;

import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.tidy.Tidy;
import org.xml.sax.SAXException;

public class Html2Pdf {

    protected Logger             log = Logger.getLogger(Html2Pdf.class);

    protected TransformerFactory transformerFactory;
    protected FopFactory         fopFactory;

    public Html2Pdf() {
        this.init();
    }

    public void init() {
        this.transformerFactory = TransformerFactory.newInstance();
        this.fopFactory = FopFactory.newInstance();
    }

    /**
     * Convert HTML into XML. Transform that XML into a PDF byte stream. Write
     * the byte stream to the response to send it to the browser.
     *
     * @param nameOfPage
     *            The name (title) of the page we are printing as PDF.
     * @param htmlOfPage
     *            The complete HTML document we convert.
     * @param response
     *            The <tt>HttpServletResponse</tt> we send the result to.
     * @param xslFileName
     *            Filename of the XSL file to use
     * @param baseURL
     *            The root of the JSPWiki install (needed for image inclusion)
     * @throws Exception
     *             If something goes wrong during the creation of the PDF.
     */
    public ByteArrayOutputStream createPDF(final String nameOfPage, final String htmlOfPage, final String xslFileName, final String baseURL,
            final HttpServletResponse response, final String encoding) throws Exception {
        // Convert HTML into XML
        InputStream in = new ByteArrayInputStream(("<title>" + nameOfPage + "</title>" + htmlOfPage).getBytes(encoding));
        Tidy tidy = new Tidy();
        tidy.setXmlOut(true);
        Document xmlDocument = tidy.parseDOM(in, null);
        log.debug("Page \"" + nameOfPage + "\" converted into XML ok.");
        // globalizeHrefs(xmlDocument, baseURL);
        // Setup a buffer to obtain the content length
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // Setup FOP
        Fop fop = this.fopFactory.newFop(MimeConstants.MIME_PDF, out);
        // Setup Transformer
        ClassLoader cl = Wiki2PdfServlet.class.getClassLoader();
        InputStream is = cl.getResourceAsStream(xslFileName);
        Source xsltSrc = new StreamSource(is);
        Transformer transformer = this.transformerFactory.newTransformer(xsltSrc);
        transformer.setParameter("baseJSPWikiURL", baseURL);
        // Make sure the XSL transformation's result is piped through to FOP
        Result res = new SAXResult(fop.getDefaultHandler());
        Source src = new DOMSource(xmlDocument);
        // Start the transformation and rendering process
        transformer.transform(src, res);
        log.debug("Page \"" + nameOfPage + "\" converted into PDF ok. Size = " + out.size());

        return out;
    }

    private void globalizeHrefs(Node node, String baseURL) {
        if (HTML_TAG_LINK.equals(node.getNodeName())) {
            NamedNodeMap map = node.getAttributes();
            Node tempNode = map.getNamedItem(HTML_HREF);
            if (!(tempNode.getNodeValue().indexOf("://") < 8 && tempNode.getNodeValue().indexOf("://") >= 0)) {
                tempNode.setNodeValue(baseURL + tempNode.getNodeValue());
                map.setNamedItem(tempNode);
            }
        }
        if (node.hasChildNodes()) {
            NodeList nl = node.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                globalizeHrefs(nl.item(i), baseURL);
            }
        }
    }

    private String parserContents(String content) throws IOException {
        Tidy tidy = createTidy();
        String result = content;
        InputStream is = null;
        ByteArrayOutputStream out = null;
        try {
            is = new ByteArrayInputStream(content.getBytes(ENCODING_ISO_8859_1));
            out = new ByteArrayOutputStream();
            // Document doc = tidy.parseDOM(is, out);
            // Clean clenan = new Clean();
            // clenan.cleanTree(paramLexer, paramNode)
            // Document doc = tidy.parseDOM(is, out);
            org.w3c.tidy.Node node = tidy.parse(is, out);
            // log.debug("parserContents()--->Node: {}", node);
            result = out.toString(ENCODING_ISO_8859_1);
            // result = readResult(result);
            // log.debug("parserContents()--->Result: {}", result);
        } catch (UnsupportedEncodingException e) {
            log.error("parserContents()--->UnsupportedEncodingException. Error: " + e.getMessage(), e);

        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e1) {
                    log.error("parserContents()--->IOException. Error: " + e1.getMessage());
                }
            }
        }
        return result;
    }

    public void createPDF(final String inputhtml, final String outputpdf, final String xslFileName) throws TransformerException, SAXException,
            IOException, ParserConfigurationException {
        this.createPDF(inputhtml, outputpdf, xslFileName, DEFAULT_BASE_URL, ENCODING_ISO_8859_1);
    }

    public void createPDF(final String inputhtml, final String outputpdf, final String xslFileName, final String baseURL, final String encoding)
            throws TransformerException, SAXException, IOException, ParserConfigurationException {
        InputStream is = null;
        InputStream isXsl = null;
        ClassLoader cl = this.getClass().getClassLoader();
        is = cl.getResourceAsStream(inputhtml);
        isXsl = cl.getResourceAsStream(xslFileName);
        OutputStream out = new FileOutputStream(outputpdf);
        // Setup FOP
        Fop fop = this.fopFactory.newFop(MimeConstants.MIME_PDF, out);
        // Setup Transformer
        Source xsltSrc = new StreamSource(isXsl);
        Transformer transformer = this.transformerFactory.newTransformer(xsltSrc);
        // transformer.setParameter("baseJSPWikiURL", baseURL);
        Tidy tidy = this.createTidy();
        Document doc = tidy.parseDOM(is, null);
        // Make sure the XSL transformation's result is piped through to FOP
        Result res = new SAXResult(fop.getDefaultHandler());
        Source src = new DOMSource(doc);
        // Start the transformation and rendering process
        transformer.transform(src, res);
    }

    private Tidy createTidy() throws IOException {
        Tidy tidy = new Tidy();
        tidy.setXHTML(true);
        tidy.setIndentContent(true);
        tidy.setUpperCaseTags(false);
        tidy.setInputEncoding(ENCODING_ISO_8859_1);
        tidy.setShowWarnings(true);
        // Drop Doctype
        tidy.setDocType(DOCTYPE_OMIT);
        // Drop:
        // <meta content="HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net" name="generator">
        // <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
        tidy.setTidyMark(false);
        return tidy;
    }

    public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
        dbfactory.setNamespaceAware(true);
        dbfactory.setXIncludeAware(false);
        return dbfactory.newDocumentBuilder();
    }

    public Document parser(InputStream xml) throws SAXException, IOException, ParserConfigurationException {
        Document doc = this.newDocumentBuilder().parse(xml);
        return doc;
    }

    private void logDoc(Document doc) throws TransformerException {
        StreamResult result = new StreamResult(new StringWriter());
        DOMSource source = new DOMSource(doc);
        TransformerFactory tFactory = TransformerFactory.newInstance();
        Transformer transformer = tFactory.newTransformer();
        transformer.transform(source, result);
        String xmlString = result.getWriter().toString();
        log.debug("logDoc()--->StringDoc: " + xmlString);
    }
}
